package com.robaone.gwt.form.client.ui;

import java.util.HashMap;
import java.util.Vector;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.Response;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteHandler;
import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
import com.google.gwt.user.client.ui.FormPanel.SubmitHandler;
import com.google.gwt.user.client.ui.Hidden;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.Node;
import com.google.gwt.xml.client.NodeList;
import com.google.gwt.xml.client.XMLParser;
/**
 * <pre>   Copyright Mar 23, 2012 Ansel Robateau
         http://www.robaone.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.</pre>
 * @author Ansel Robateau
 *
 */
public class FormUi extends Composite {

	public static final String TITLE = "title";
	public static final String NAME = "name";
	public static final String TYPE = "type";
	public static final String DESCRIPTION = "description";
	public static final String REQUIRED = "required";
	public static final String HELP = "help";
	public static final String ITEMS = "items";
	public static final String VALUE = "value";
	protected static final String ACTION = "action";
	protected static final String METHOD = "method";
	protected static final String LABEL = "label";
	protected static final String ID = "id";
	protected static final String ONSUCCESS = "onsuccess";
	protected static final String HIDDEN = "hidden";
	private static final String RESPONSE = "response";
	private static final String STATUS = "status";
	private static final String ERRORS = "errors";
	private static final String ERROR = "error";
	public static final String AUTHENTICATION_URL = "auth_url";
	public static enum TYPES {CheckBox,List,MultiSelectList,Radio,TextArea,Text, Password, Hidden, FileUpload};

	public static final int OK = 0;
	public static final int FIELD_VALIDATION_ERROR = 2;
	public static final int LOGIN_REQUIRED = 3;
	public static final int GENERAL_ERROR = 1;
	protected static final String STYLENAME = "class";

	private static FormUiUiBinder uiBinder = GWT.create(FormUiUiBinder.class);

	interface FormUiUiBinder extends UiBinder<Widget, FormUi> {
	}

	public FormUi() {
		initWidget(uiBinder.createAndBindUi(this));
		this.errors.setVisible(false);
	}
	@UiField FormPanel form;
	@UiField FlowPanel buttonpanel;
	@UiField Label title;
	@UiField FlowPanel errors;
	@UiField Label description;
	@UiField FlowPanel fields;

	HashMap<String,Button> buttons = new HashMap<String,Button>();
	HashMap<String,FormFieldUi> m_fieldmap = new HashMap<String,FormFieldUi>();
	private SubmitCompleteHandler m_submitcompletehandler;
	private String m_successurl;
	private SubmitHandler m_submithandler;
	private boolean m_submitting;
	protected String m_action;
	private String m_authenticationurl;
	private String m_stylename = null;
	private String m_addedstyle;
	public Button getButton(String id){
		return buttons.get(id);
	}
	public void setTitle(String str){
		this.title.setText(str);
	}
	public void setErrors(Widget[] messages){
		errors.clear();
		if(messages == null || messages.length == 0){
			errors.setVisible(false);
		}else{
			for(int i = 0; i < messages.length;i++){
				errors.add(messages[i]);
			}
			errors.setVisible(true);
		}
	}
	public void setDescription(String str){
		this.description.setText(str);
	}
	public void addField(FormFieldUi field){
		fields.add(field);
		this.m_fieldmap.put(field.getName(), field);
	}
	public HashMap<String,String[]> getFormData(){
		HashMap<String,String[]> retval = new HashMap<String,String[]>();
		for(int i = 0; i < this.fields.getWidgetCount();i++){
			Widget w = this.fields.getWidget(i);
			if(w instanceof FormFieldUi){
				FormFieldUi field = (FormFieldUi)w;
				String[] values = field.getValues();
				String name = field.getName();
				retval.put(name,values);
			}
		}
		return retval;
	}
	public void addField(String[][] field_def) throws Exception{
		HashMap<String,String[]> retval = new HashMap<String,String[]>();
		for(int i = 0; i < field_def.length;i++){
			String[] val = new String[field_def[i].length -1];
			for(int ii = 0; ii < field_def[i].length -1 ; ii++){
				val[ii] = field_def[i][ii+1];
			}
			retval.put(field_def[i][0], val);
		}
		addField(retval);
	}
	@Override
	public void setStyleName(String name){
		super.setStyleName(name);
		this.form.setStyleName(name);
		this.buttonpanel.setStyleName(name+"-buttons");
		this.description.setStyleName(name+"-description");
		this.errors.setStyleName(name+"-error");
		this.fields.setStyleName(name+"-fields");
		this.title.setStyleName(name+"-title");
		this.m_stylename = name;
	}
	@Override
	public void addStyleName(String name){
		super.addStyleName(name);
		this.form.addStyleName(name);
		this.buttonpanel.addStyleName(name+"-buttons");
		this.description.addStyleName(name+"-description");
		this.errors.addStyleName(name+"-error");
		this.fields.addStyleName(name+"-fields");
		this.title.addStyleName(name+"-title");
		this.m_addedstyle = name;
	}
	public void addField(HashMap<String, String[]> info) throws Exception {
		FormFieldUi field = new FormFieldUi();
		if(this.m_stylename != null){
			field.setStyleName(this.m_stylename+"-field");
		}
		field.setTitle(info.get(TITLE) != null? info.get(TITLE)[0] : null);
		field.setDescription(info.get(DESCRIPTION) != null ? info.get(DESCRIPTION)[0] : null);
		field.setInfo(info.get(HELP) != null ? info.get(HELP)[0] : null);
		try{field.setRequired(info.get(REQUIRED) != null ? info.get(REQUIRED)[0].equals("true") : false);}catch(Exception e){
			field.setRequired(false);
		}
		String[] values = null;
		try{values = info.get(VALUE);}catch(Exception e){}
		if(values != null && values.length == 0){
			values = null;
		}
		String[] items = null;
		try{items = info.get(ITEMS);}catch(Exception e){}
		String type = info.get(TYPE) != null ? info.get(TYPE)[0] : null;
		if(type == null) return;
		if(type.equalsIgnoreCase(TYPES.List.toString())){
			ListFieldUi item = new ListFieldUi(info.get(NAME)[0]);
			if(this.m_stylename != null){
				item.setStyleName(this.m_stylename+"-list");
			}
			addStyle(info, item);
			try{item.setValue(values,items);}catch(Exception e){}
			field.setField(item);
		}else if(type.equalsIgnoreCase(TYPES.MultiSelectList.toString())){
			MultiSelectListFieldUi item = new MultiSelectListFieldUi(info.get(NAME)[0]);
			item.getListBox().setVisibleItemCount(3);
			if(this.m_stylename != null){
				item.setStyleName(this.m_stylename="-list");
			}
			addStyle(info,item);
			try{item.setValue(values,items);}catch(Exception e){}
			field.setField(item);
		}else if(type.equalsIgnoreCase(TYPES.CheckBox.toString())){
			CheckFieldUi item = new CheckFieldUi(info.get(NAME)[0]);
			if(this.m_stylename != null){
				item.setStyleName(this.m_stylename+"-check");
			}
			addStyle(info, item);
			try{item.setValues(values,items);}catch(Exception e){}
			field.setField(item);
		}else if(type.equalsIgnoreCase(TYPES.Radio.toString())){
			RadioFieldUi item = new RadioFieldUi(info.get(NAME)[0]);
			if(this.m_stylename != null){
				item.setStyleName(this.m_stylename+"-radio");
			}
			addStyle(info, item);
			try{item.setValue(values != null ? values[0] : null, items);}catch(Exception e){}
			field.setField(item);
		}else if(type.equalsIgnoreCase(TYPES.Text.toString())){
			TextFieldUi item = new TextFieldUi(info.get(NAME)[0]);
			if(this.m_stylename != null){
				item.setStyleName(this.m_stylename+"-text");
			}
			addStyle(info, item);
			item.setText(values != null ? values[0] : null);
			item.getElement().setAttribute(NAME, info.get(NAME)[0]);
			field.setField(item);
		}else if(type.equalsIgnoreCase(TYPES.TextArea.toString())){
			TextAreaFieldUi item = new TextAreaFieldUi(info.get(NAME)[0]);
			if(this.m_stylename != null){
				item.setStyleName(this.m_stylename+"-textarea");
			}
			addStyle(info, item);
			item.setText(values != null ? values[0] : null);
			field.setField(item);
		}else if(type.equalsIgnoreCase(TYPES.Password.toString())){
			PasswordFieldUi item = new PasswordFieldUi(info.get(NAME)[0]);
			if(this.m_stylename != null){
				item.setStyleName(this.m_stylename+"-password");
			}
			addStyle(info, item);
			item.setText(values != null ? values[0] : null);
			field.setField(item);
		}else if(type.equalsIgnoreCase(TYPES.Hidden.toString())){
			HiddenFieldUi item = new HiddenFieldUi(info.get(NAME)[0]);
			item.setText(values != null ? values[0] : null);
			field.setVisible(false);
			field.setField(item);
		}else if(type.equalsIgnoreCase(TYPES.FileUpload.toString())){
			FileUploadFieldUi item = new FileUploadFieldUi(info.get(NAME)[0]);
			field.setField(item);
			this.form.setEncoding(FormPanel.ENCODING_MULTIPART);
		}else{
			System.out.println("Unknown Type");
		}
		this.fields.add(field);
		this.m_fieldmap.put(field.getName(), field);
	}
	private void addStyle(HashMap<String, String[]> info, UIObject item) {
		if(info.get(STYLENAME) != null && info.get(STYLENAME).length > 0){
			item.addStyleName(info.get(STYLENAME)[0]);
		}
		if(this.m_addedstyle != null && this.m_addedstyle.length() > 0){
			item.addStyleName(this.m_addedstyle);
		}
	}
	public void clear(){
		this.fields.clear();
		this.m_fieldmap.clear();
	}
	public FormFieldUi getField(String name){
		return this.m_fieldmap.get(name);
	}
	public void setFields(Vector<HashMap<String, String[]>> fields2) throws Exception {
		for(int i = 0; i < fields2.size();i++){
			addField(fields2.get(i));
		}
	}
	public void setFieldError(String name,String error){
		FormFieldUi field = this.m_fieldmap.get(name);
		if(field != null){
			field.setError(error);
		}
	}
	public void clearErrors(){
		this.setErrors(null);
		String[] keys = this.m_fieldmap.keySet().toArray(new String[0]);
		for(int i = 0; i < keys.length;i++){
			FormFieldUi field = this.m_fieldmap.get(keys[i]);
			field.clearError();
		}
	}
	public void addFieldKeyUpHandler(String name,KeyUpHandler handler){
		FormFieldUi field = this.m_fieldmap.get(name);
		field.addKeyUpHandler(handler);
	}
	/*****
	 * Description: Load the xml representation into this object
	 * Author: Ansel Robatau
	 * Copyright: Robaone Consulting 2011
	 * 
	 * @param String document_url
	 */
	public void load(String document_url){
		try{
			form.addSubmitCompleteHandler(getSubmitCompleteHandler());
			form.addSubmitHandler(this.getSubmitHandler());
			RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, document_url);
			rb.sendRequest(null, new RequestCallback(){
				private HashMap<String, String[]> getMapforFields(String[][] first_name) {
					HashMap<String,String[]> retval = new HashMap<String,String[]>();
					for(int i = 0; i < first_name.length;i++){
						String[] val = new String[first_name[i].length-1];
						for(int ii = 0; ii < first_name[i].length-1;ii ++){
							val[ii] = first_name[i][ii+1];
						}
						retval.put(first_name[i][0], val);
					}
					return retval;
				}
				@Override
				public void onResponseReceived(Request request,
						Response response) {
					try{
						int code = response.getStatusCode();
						if(code == 200){
							String str = response.getText();
							if(str.startsWith("<?")){
								System.out.println("Reading xml file");
								// parse the XML document into a DOM
								Document messageDom = XMLParser.parse(str);
								/**
								 * Retrieve the root
								 */
								Node formui = messageDom.getElementsByTagName("formui").item(0);
								/**
								 * Retrieve the form action
								 */
								try{
									String action = formui.getAttributes().getNamedItem(ACTION).getNodeValue();
									FormUi.this.setAction(action);
								}catch(Exception e){}
								/**
								 * Retrieve the form authentication url
								 */
								try{
									String url = formui.getAttributes().getNamedItem(AUTHENTICATION_URL).getNodeValue();
									FormUi.this.setAuthenticationUrl(url);
								}catch(Exception e){}
								/**
								 * Retrieve the form submit method
								 */
								try{
									String method = formui.getAttributes().getNamedItem(METHOD).getNodeValue();
									FormUi.this.form.setMethod(method);
								}catch(Exception e){}
								/**
								 * Retrieve the success url
								 */
								try{
									String onsuccess = formui.getAttributes().getNamedItem(ONSUCCESS).getNodeValue();
									setOnSuccess(onsuccess);
								}catch(Exception e){}
								/**
								 * Retrieve the form style class
								 */
								try{
									String styleName = formui.getAttributes().getNamedItem(STYLENAME).getNodeValue();
									setStyleName(styleName);
								}catch(Exception e){}
								/**
								 * Retrieve the form title
								 */
								try{
									title.setText(findElement(formui,"title").get(0).getFirstChild().getNodeValue());
								}catch(Exception e){}
								/**
								 * Retrieve the form description
								 */
								try{
									description.setText(findElement(formui,"description").get(0).getFirstChild().getNodeValue());
								}catch(Exception e){}
								/**
								 * Retrieve the fields
								 */
								Vector<Node> form_fields = findElement(formui,"field");
								Vector<HashMap<String,String[]>> fields = new Vector<HashMap<String,String[]>>();
								for(int i = 0; i < form_fields.size();i++){
									Node field_node = form_fields.get(i);
									String[][] field_properties = new String[9][];
									for(int j = 0; j < 9;j++){
										field_properties[j] = new String[2];
									}
									field_properties[0][0] = FormUi.TITLE;
									try{
										field_properties[0][1] = findElement(field_node,"title").get(0).getFirstChild().getNodeValue();
									}catch(Exception e){}
									field_properties[1][0] = FormUi.NAME;
									try{
										field_properties[1][1] = field_node.getAttributes().getNamedItem("name").getNodeValue();
									}catch(Exception e){}
									field_properties[2][0] = FormUi.TYPE;
									try{
										field_properties[2][1] = field_node.getAttributes().getNamedItem("type").getNodeValue();
									}catch(Exception e){}
									field_properties[3][0] = FormUi.DESCRIPTION;
									try{
										field_properties[3][1] = findElement(field_node,"description").get(0).getFirstChild().getNodeValue();
									}catch(Exception e){}
									field_properties[4][0] = FormUi.REQUIRED;
									try{
										String val = field_node.getAttributes().getNamedItem("required").getNodeValue();
										field_properties[4][1] = val;
									}catch(Exception e){}
									field_properties[5][0] = FormUi.HELP;
									try{
										field_properties[5][1] = findElement(field_node,"help").get(0).getFirstChild().getNodeValue();
									}catch(Exception e){}
									try{
										Vector<Node> values = findElement(field_node,"value");
										field_properties[6] = new String[values.size() + 1];
										field_properties[6][0] = FormUi.VALUE;
										for(int k = 0; k < values.size();k++){
											field_properties[6][k+1] = values.get(k).getFirstChild().getNodeValue();
										}
									}catch(Exception e){}
									try{
										Vector<Node> items = findElement(field_node,"item");
										field_properties[7] = new String[items.size() + 1];
										field_properties[7][0] = FormUi.ITEMS;
										for(int k = 0; k < items.size();k++){
											String label = null;
											try{
												label = items.get(k).getAttributes().getNamedItem("label").getNodeValue();
											}catch(Exception e){}
											String[] nodevalue = new String[2];
											nodevalue[0] = items.get(k).getFirstChild().getNodeValue();
											nodevalue[1] = label;
											JSONObject jo = new JSONObject();
											jo.put("value",new JSONString( nodevalue[0]));
											jo.put("label", new JSONString(label));
											field_properties[7][k+1] = jo.toString(); 
										}
									}catch(Exception e){}
									try{
										String stylename = field_node.getAttributes().getNamedItem(STYLENAME).getNodeValue();
										field_properties[8][0] = FormUi.STYLENAME;
										field_properties[8][1] = stylename;
									}catch(Exception e){}
									fields.add(this.getMapforFields(field_properties));
								}
								setFields(fields);

								/**
								 * Configure the buttons
								 */
								NodeList buttons = messageDom.getElementsByTagName("button");
								for(int i = 0; i < buttons.getLength();i++){
									final Node node = buttons.item(i);
									Button button = new Button();
									button.setText(node.getAttributes().getNamedItem(LABEL).getNodeValue());
									String action = null;
									try{
										action = node.getAttributes().getNamedItem(ACTION).getNodeValue();
										if(action == null || action.length() == 0){
											action = form.getAction();
										}
									}catch(Exception e){
										action = form.getAction();
									}
									try{
										String hidden = node.getAttributes().getNamedItem(HIDDEN).getNodeValue();
										if("true".equals(hidden)){
											button.setVisible(false);
										}
									}catch(Exception e){}
									String onsuccess = null;
									try{
										onsuccess = node.getAttributes().getNamedItem(ONSUCCESS).getNodeValue();
										if(onsuccess == null || onsuccess.length() == 0){
											onsuccess = FormUi.this.m_successurl;
										}
									}catch(Exception e){
										onsuccess = FormUi.this.m_successurl;
									}
									if(action != null){
										final String successaction = onsuccess;
										final String buttonaction = action;
										button.addClickHandler(new ClickHandler(){

											@Override
											public void onClick(ClickEvent event) {
												setAction(buttonaction);
												if(successaction != null) setOnSuccess(successaction);
												form.submit();
											}

										});
									}
									setButton(node.getAttributes().getNamedItem(ID).getNodeValue(),button);
								}
							}else{
								throw new Exception("Invalid Response");
							}
						}else{
							throw new Exception(response.getStatusText());
						}
					}catch(Exception e){
						onError(request,e);
					}
				}

				@Override
				public void onError(Request request, Throwable exception) {
					clearErrors();
					Vector<Widget> errors = new Vector<Widget>();
					Label error = new Label(exception.getClass().getName()+": "+exception.getMessage());
					errors.add(error);
					setErrors(errors.toArray(new Widget[0]));
				}

			});
		}catch(Exception e){
			this.clearErrors();
			Vector<Widget> errors = new Vector<Widget>();
			Label error = new Label(e.getClass().getName()+": "+e.getMessage());
			errors.add(error);
			this.setErrors(errors.toArray(new Widget[0]));
		}
	}
	private SubmitHandler getSubmitHandler() {
		if(this.m_submithandler == null){
			this.m_submithandler = this.newSubmitHandler();
		}
		return this.m_submithandler;
	}
	private SubmitHandler newSubmitHandler() {
		return new SubmitHandler(){

			@Override
			public void onSubmit(SubmitEvent event) {
				System.out.println("SubmitHandler.submitting = "+isSubmitting());
				if(isSubmitting() == false){
					FormUi.this.clearErrors();
					setSubmitting(true);
				}else{
					event.cancel();
					form.setAction("");
				}
			}

		};
	}
	public void setAction(String action){
		this.m_action = action;
		form.setAction(action);
	}
	public void setSubmitting(boolean val){
		this.m_submitting = val;
		if(val){

		}else{

		}
	}
	public boolean isSubmitting(){
		return this.m_submitting;
	}
	public void setOnSuccess(String onsuccess){
		this.m_successurl = onsuccess;
	}
	protected SubmitCompleteHandler newSubmitCompleteHandler(){
		SubmitCompleteHandler h = new FormSubmitCompleteHandler(this);
		this.m_submitcompletehandler = h;
		return h;
	}
	protected void addError(String string) {
		if(string != null && string.trim().length() > 0){
			Label l = new Label(string);
			Widget[] w = new Widget[1];
			w[0] = l;
			this.setErrors(w);
		}
	}
	protected SubmitCompleteHandler getSubmitCompleteHandler(){
		if(this.m_submitcompletehandler == null){
			this.m_submitcompletehandler = this.newSubmitCompleteHandler();
		}
		return this.m_submitcompletehandler;
	}
	protected void setButton(String nodeValue, Button button) {
		this.buttonpanel.add(button);
		this.buttons.put(nodeValue,button);
	}
	protected Vector<Node> findElement(Node formui, String string) {
		Vector<Node> retval = new Vector<Node>();
		for(int i = 0; i < formui.getChildNodes().getLength();i++){
			Node child = formui.getChildNodes().item(i);
			if(child.getNodeName().equals(string)){
				retval.add(child);
			}
		}
		return retval;
	}
	public FormPanel getForm() {
		return form;
	}
	/**
	 * @return
	 */
	public String getAction() {
		return this.m_action;
	}
	/**
	 * @return
	 */
	public String getSuccessUrl() {
		return this.m_successurl;
	}
	/**
	 * @param jsonValue
	 */
	public void update(JSONValue jsonValue) {
		try{
			JSONValue response = jsonValue.isObject().get(RESPONSE);
			JSONValue status = response.isObject().get(STATUS);
			if(status.isNumber().doubleValue() == OK){
				Window.Location.assign(getSuccessUrl());
				return;
			}else if(status.isNumber().doubleValue() == FIELD_VALIDATION_ERROR || 
					status.isNumber().doubleValue() == GENERAL_ERROR){
				/**
				 * Update fields with error messages.
				 */
				JSONValue errors = response.isObject().get(ERRORS);
				for(int i = 0; i < errors.isObject().keySet().size();i++){
					String key = errors.isObject().keySet().toArray(new String[0])[i];
					this.setFieldError(key, errors.isObject().get(key).isString().stringValue());
				}
			}else if(status.isNumber().doubleValue() == LOGIN_REQUIRED){
				if(getAuthenticationUrl() != null){
					Window.Location.assign(getAuthenticationUrl());
				}else{
					addError("Login Required");
				}
			}
			try{
				JSONValue generalError = response.isObject().get(ERROR);
				this.addError(generalError.isString().stringValue());
			}catch(Exception e){}
		}catch(Exception e){
			this.addError(e.getMessage());
		}
	}
	/**
	 * @return
	 */
	public String getAuthenticationUrl() {
		return m_authenticationurl;
	}
	public void setAuthenticationUrl(String url){
		this.m_authenticationurl = url;
	}
}
